/**
 * 
 */
package com.sfeir.client.activity;

import static com.sfeir.client.util.Constants.BUTTON_HEIGHT;
import static com.sfeir.client.util.Constants.BUTTON_WIDTH;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Image;
import com.google.web.bindery.event.shared.EventBus;
import com.sfeir.canvas.activities.client.AbstractCanvasActivity;
import com.sfeir.canvas.activities.client.event.PageChangeEvent;
import com.sfeir.canvas.util.client.ResourceManager;
import com.sfeir.client.service.AppServiceAsync;
import com.sfeir.client.util.Constants;
import com.sfeir.shared.Level;

/**
 * Activity used to pick a level. <br />
 * It will display the levels on the canvas, handle mouse over and will launch
 * the game from the clicked
 * 
 * @author François LAROCHE
 * @author Oussama Zoghlami
 */
public class LevelPickerActivity extends AbstractCanvasActivity {

	private static final int THRESHOLD = 10;

	private static final int NB_LEVEL_PER_LINE = 6;
	private static final int LEVEL_WIDTH = 100;
	private static final int LEVEL_HEIGHT = 95;
	private static final int LEVEL_IMAGE_HEIGTH = 70;
	private static final int LEVEL_SPACING = 30;
	private static final int BACK_BUTTON_X = 15;
	private static final int BACK_BUTTON_Y = Constants.CANVAS_HEIGHT - 80;

	private final AppServiceAsync service;
	private Context2d ctx;
	private EventBus bus;
	private List<Map<String, Object>> userLevels = new ArrayList<Map<String, Object>>();
	private int selectedIndex;
	private int currentX;
	private int currentY;
	private final Image background;
	private final Image back;
	private final Image star;
	private final Image emptyStar;
	private final Image lock;
	private final ResourceManager manager;

	public LevelPickerActivity(ResourceManager manager, AppServiceAsync service) {
		this.service = service;
		this.background = manager.getImage(Constants.KEY_IMAGE_BACKGROUND);
		this.back = manager.getImage(Constants.KEY_IMAGE_BACK);
		this.star = manager.getImage(Constants.KEY_IMAGE_STAR_SMALL);
		this.emptyStar = manager.getImage(Constants.KEY_IMAGE_STAR_EMPTY);
		this.lock = manager.getImage(Constants.KEY_IMAGE_LOCK);
		this.manager = manager;
	}

	@Override
	public void start(Canvas canvas, EventBus bus) {
		// Initialize object
		this.ctx = canvas.getContext2d();
		this.bus = bus;
		this.currentX = -1;
		this.currentY = -1;
		this.selectedIndex = -1;
		paint();
		getGameLevels();
	}

	/**
	 * Method allowing to paint the activity
	 */
	private void paint() {
		drawBackGroundImage();
		drawBackImage();
	}

	/**
	 * Method allowing to draw the background image
	 */
	private void drawBackGroundImage() {
		ctx.drawImage(ImageElement.as(this.background.getElement()), 0, 0);
	}

	/**
	 * Draw a back image
	 */
	private void drawBackImage() {
		ctx.drawImage(ImageElement.as(this.back.getElement()), BACK_BUTTON_X, BACK_BUTTON_Y);
	}

	/**
	 * Method allowing to get the game levels
	 */
	private void getGameLevels() {
		// Load levels and paint them
		this.service.getUserLevels(new AsyncCallback<List<Map<String, Object>>>() {

			@Override
			public void onSuccess(List<Map<String, Object>> result) {
				userLevels = result;
				for (int i = 0; i < result.size(); i++) {
					paintLevel(i, false);
				}
			}

			@Override
			public void onFailure(Throwable caught) {
				caught.printStackTrace();
			}
		});

	}

	/**
	 * Resolve the index in the list of the level corresponding to the position
	 * of the mouse. If the mouse isn't above a level, -1 is returned
	 * 
	 * @param x
	 *            the x position of the mouse on the canvas
	 * @param y
	 *            the y position of the mouse on the canvas
	 * @return the theorical index for the mouse
	 */
	private int resolveSelectionIndex(int x, int y) {
		// Check if mouse isn't in the middle of nowhere
		int moduloX = x % (LEVEL_SPACING + LEVEL_WIDTH);
		if (moduloX < LEVEL_SPACING) {
			return -1;
		}
		int moduloY = y % (LEVEL_SPACING + LEVEL_HEIGHT);
		if (moduloY < LEVEL_SPACING) {
			return -1;
		}

		int column = (x - moduloX) / (LEVEL_SPACING + LEVEL_WIDTH);
		int line = (y - moduloY) / (LEVEL_SPACING + LEVEL_HEIGHT);

		return column + NB_LEVEL_PER_LINE * line;
	}

	/**
	 * checks whether the selection has changed. If it has changed, the levels
	 * changed are repainted accordingly
	 */
	private void checkSelection() {
		int index = resolveSelectionIndex(this.currentX, this.currentY);
		if (this.userLevels == null) {
			return;
		}
		if (index >= this.userLevels.size()) {
			index = -1;
		}

		if (index != this.selectedIndex) {
			paintLevel(this.selectedIndex, false);
			this.selectedIndex = index;
			paintLevel(selectedIndex, true);
		}
	}

	/**
	 * Paint / repaint a level on the canvas
	 * 
	 * @param index
	 *            the index of the level in the list
	 * @param selected
	 *            whether this level is selected or not
	 */
	private void paintLevel(int index, boolean selected) {
		if (index == -1) {
			return;
		}
		// compute case :
		// 1- compute x,y on grid
		int x = index % NB_LEVEL_PER_LINE;
		int y = (index - x) / NB_LEVEL_PER_LINE;

		// 2- get real coordinates
		x *= (LEVEL_SPACING + LEVEL_WIDTH);
		x += LEVEL_SPACING;
		y *= (LEVEL_SPACING + LEVEL_HEIGHT);
		y += LEVEL_SPACING;

		this.ctx.save();
		this.ctx.setFillStyle("white");
		this.ctx.fillRect(x - 6, y - 6, LEVEL_WIDTH + 12, LEVEL_HEIGHT + 12);

		Integer unlocked = (Integer) userLevels.get(index).get(Constants.MAP_ISUNLOCKED_KEY);
		if (unlocked == 0) {
			drawLevelImage(index, x, y);
			drawLevelStars(index, x, y);
			if (selected) {
				this.ctx.setLineWidth(5);
				this.ctx.setStrokeStyle("#848484");
				this.ctx.strokeRect(x - 4, y - 4, LEVEL_WIDTH + 8, LEVEL_HEIGHT + 8);
			}
		} else {
			this.ctx.setLineWidth(5);
			this.ctx.drawImage(ImageElement.as(lock.getElement()), x + 20, y + 13, 60, 60);
		}

		this.ctx.setStrokeStyle("black");
		this.ctx.strokeRect(x, y, LEVEL_WIDTH, LEVEL_HEIGHT);
		this.ctx.restore();
	}

	/**
	 * Method allowing to draw the image associated to a given level index and
	 * x,y coordinates
	 * 
	 * @param index
	 * @param x
	 * @param y
	 */
	private void drawLevelImage(int index, int x, int y) {
		this.ctx.setLineWidth(5);
		Level paintedLevel = (Level) this.userLevels.get(index).get(Constants.MAP_LEVEL_KEY);
		int levelOrder = paintedLevel.getOrder();
		int indexDiv = levelOrder / 10;
		int indexMod = levelOrder % 10;

		/**
		 * if the level index contain only one digit (example : level 1), then
		 * we will draw the image associated to the digit
		 */
		if (indexDiv == 0) {
			Image levelImage = manager.getImage("level" + indexMod);
			this.ctx.drawImage(ImageElement.as(levelImage.getElement()), x + 15, y + 5);
		}
		/**
		 * if the level index contain two digit (example: level 21), then we
		 * will draw two images, one associated to the (levelindex div 10) digit
		 * and the other associated to the level (levelindex mod 10) digit
		 */
		else {
			Image levelImage1 = manager.getImage("level" + indexDiv);
			Image levelImage2 = manager.getImage("level" + indexMod);
			this.ctx.drawImage(ImageElement.as(levelImage1.getElement()), x + 10, y + 5, 40, LEVEL_IMAGE_HEIGTH);
			this.ctx.drawImage(ImageElement.as(levelImage2.getElement()), x + 40, y + 5, 40, LEVEL_IMAGE_HEIGTH);
		}
	}

	/**
	 * Method allowing to draw the stars number associated to a given level and
	 * to a given reference coordinate
	 * 
	 * @param index
	 * @param x
	 * @param y
	 */
	private void drawLevelStars(int index, int x, int y) {
		Integer starNumber = (Integer) userLevels.get(index).get(Constants.MAP_STARS_KEY);
		for (int i = 0; i < starNumber; i++) {
			this.ctx.drawImage(ImageElement.as(star.getElement()), x + 15 + i * 25, y + 65, 20, 20);
		}

		for (int i = starNumber; i < Constants.STARS_TOTAL_NUMBER; i++) {
			this.ctx.drawImage(ImageElement.as(emptyStar.getElement()), x + 15 + i * 25, y + 65, 20, 20);
		}
	}

	@Override
	public void onClick(ClickEvent event) {
		int x = event.getX();
		int y = event.getY();
		// Click on the back button
		if (x >= BACK_BUTTON_X && x <= BACK_BUTTON_X + BUTTON_WIDTH && y >= BACK_BUTTON_Y
				&& y <= BACK_BUTTON_Y + BUTTON_HEIGHT) {
			this.bus.fireEvent(new PageChangeEvent(PageChangeEvent.BACK, null));
			return;
		}
		if (this.selectedIndex != -1 && selectedIndex < this.userLevels.size()) {
			if ((Integer) this.userLevels.get(selectedIndex).get(Constants.MAP_ISUNLOCKED_KEY) == 0) {
				Level lvl = (Level) this.userLevels.get(selectedIndex).get(Constants.MAP_LEVEL_KEY);
				Map<String, Object> context = new HashMap<String, Object>();
				context.put(Constants.LEVEL_PARAM, lvl);
				PageChangeEvent evt = new PageChangeEvent(PageChangeEvent.NEXT, context);
				this.bus.fireEvent(evt);
			}
		}
	}

	@Override
	public void onMouseMove(MouseMoveEvent event) {
		if (this.currentX == -1
				|| Math.abs(this.currentX - event.getX()) + Math.abs(this.currentY - event.getY()) > THRESHOLD) {
			this.currentX = event.getX();
			this.currentY = event.getY();
			checkSelection();
		}
	}

}
